package com.acme.concurrent.pool.impl;

import com.acme.concurrent.pool.DataSource;
import com.acme.concurrent.pool.DataSourceException;
import com.acme.concurrent.pool.NonBlockingPool;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SimpleSemaphorePool<T> implements NonBlockingPool<T> {

    private final Semaphore semaphore;
    private final Map<T, Boolean> storage;

    private final Lock lock = new ReentrantLock();

    public SimpleSemaphorePool(DataSource<T> dataSource, int max) throws DataSourceException {
        semaphore = new Semaphore(max);
        storage = new HashMap<T, Boolean>(max);
        for(int i = 0; i < max; i++){
            storage.put(dataSource.newInstance(), true);
        }
        if (storage.size() != max) {
            throw new RuntimeException("DataSource created two or more similar objects");
        }
    }

    @Override
    public T get() throws InterruptedException {
        semaphore.acquire();
        return getNextFreeObject();
    }

    @Override
    public void release(T object) {
        if(putObject(object))
            semaphore.release();
    }

    @Override
    public T tryGet() {
        if(semaphore.tryAcquire()){
            return getNextFreeObject();
        }else{
            return null;
        }

    }

    @Override
    public T tryGet(long timeout, TimeUnit timeUnit) throws InterruptedException {
        if(semaphore.tryAcquire(timeout, timeUnit)){
            return getNextFreeObject();
        }else{
            return null;
        }
    }

    private T getNextFreeObject(){
        try{
            lock.lock();
            for(Map.Entry<T, Boolean> entry: storage.entrySet()){
                if(entry.getValue()){
                    entry.setValue(false);
                    return entry.getKey();
                }
            }
            throw new RuntimeException("Ooops, you should never get there");
        }finally {
            lock.unlock();
        }
    }

    private boolean putObject(T object){
        try{
            lock.lock();
            if(storage.containsKey(object)){
                return !storage.put(object, true);
            }
            return false;
        }finally {
            lock.unlock();
        }
    }

}